<?xml version="1.0" encoding="UTF-8"?>
<!--
   - xstdoc2xsl.xsl
   - Preprocess tag document to XSLT
   -
   - Copyright (c) 2011 Erik Hennum. All Rights Reserved.
   -
   - Licensed under the Apache License, Version 2.0 (the "License");
   - you may not use this file except in compliance with the License.
   - You may obtain a copy of the License at
   -
   - http://www.apache.org/licenses/LICENSE-2.0
   -
   - Unless required by applicable law or agreed to in writing, software
   - distributed under the License is distributed on an "AS IS" BASIS,
   - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   - See the License for the specific language governing permissions and
   - limitations under the License.
   -
   - The use of the Apache License does not indicate that this work is
   - associated with the Apache Software Foundation.
   -
  -->
<xsl:stylesheet version="2.0"
    xmlns:xsl = "http://www.w3.org/1999/XSL/Transform"
    xmlns:osl = "file:///www.w3.org/1999/XSL/Transform"
    xmlns:fn  = "http://www.w3.org/2005/xpath-functions"
    xmlns:xs  = "http://www.w3.org/2001/XMLSchema"
    xmlns:xst = "http://code.google.com/p/xstag/ns/2011/"
    >

<xsl:import href="xst2xslutil.xsl"/>

<xsl:namespace-alias stylesheet-prefix="osl" result-prefix="xsl"/>


<!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  - Launch tag doc conversion
  - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -->
<xsl:template match="/">
  <xsl:variable name="docroot" select="*"/>

  <xsl:variable name="doc-params" as="element(xst:param)*"
          select="$docroot//xst:param"/>

  <xsl:variable name="namespaces" as="element()*">
    <xsl:for-each select="fn:in-scope-prefixes($docroot)">
      <namespace prefix="{.}" uri="{fn:namespace-uri-for-prefix(., $docroot)}"/>
    </xsl:for-each>
  </xsl:variable>

  <xsl:variable name="imports" as="element(xst:import)*"
          select="$docroot//xst:import[fn:empty(@for) or @for = ('xstag', 'xslt')]"/>
  <xsl:if test="fn:empty($imports)">
    <xsl:message terminate="yes">
      <xsl:text>No tag library imports in </xsl:text>
      <xsl:value-of select="$xst:module-uri"/>
    </xsl:message>
  </xsl:if>

  <xsl:variable name="taglibs" as="element(xst:taglib)*"
          select="xst:import-taglibs($imports[fn:empty(@for) or @for eq 'xstag'])"/>
  <xsl:if test="fn:empty($taglibs)">
    <xsl:message terminate="yes">
      <xsl:text>Could not resolve tag library imports in </xsl:text>
      <xsl:value-of select="$xst:module-uri"/>
      <xsl:text> for </xsl:text>
      <xsl:value-of select="fn:string-join($imports/@href,', ')"/>
    </xsl:message>
  </xsl:if>

  <xsl:variable name="tagdefs" as="element(xst:tag)*"
          select="xst:extract-tagdefs($taglibs,$namespaces)"/>
  <xsl:if test="fn:empty($tagdefs)">
    <xsl:message terminate="yes">
      <xsl:text>No tag definitions found in tag library imports by </xsl:text>
      <xsl:value-of select="$xst:module-uri"/>
      <xsl:text> from </xsl:text>
      <xsl:value-of select="fn:string-join($imports/@href,', ')"/>
    </xsl:message>
  </xsl:if>

  <xsl:variable name="handler-tags" as="element(xst:tag)*"
        select="$tagdefs[fn:exists(
            xst:param[fn:starts-with(fn:normalize-space(@as),'handler(')]
            )]"/>

  <xsl:variable name="handler-calls" as="element()*"
        select="if (fn:exists($handler-tags))
            then //*[fn:name(.)=$handler-tags/@imp-qname]
            else ()"/>

  <xsl:text>
</xsl:text>
  <xsl:comment>GENERATED by xstdoc2xsl.xsl - please do not edit</xsl:comment>
  <xsl:text>
</xsl:text>
  <osl:stylesheet version="2.0">
    <xsl:for-each select="$namespaces">
      <xsl:namespace name="{@prefix}" select="@uri"/>
    </xsl:for-each>
    <xsl:text>
</xsl:text>

    <xsl:for-each select="$imports">
      <xsl:variable name="href">
        <xsl:choose>
        <xsl:when test="@for eq 'xslt'">
          <xsl:value-of
              select="@href"/>
        </xsl:when>
        <xsl:otherwise>
          <xsl:value-of
              select="fn:concat(fn:replace(@href,'^(.*?)\.[^.]+$','$1'),'.xsl')"/>
        </xsl:otherwise>
        </xsl:choose>
      </xsl:variable>
      <osl:import href="{$href}"/>
      <xsl:text>
</xsl:text>
    </xsl:for-each>

    <xsl:for-each select="$doc-params">
      <osl:param>
        <xsl:copy-of select="@*"/>
        <xsl:copy-of select="node()"/>
      </osl:param>
      <xsl:text>
</xsl:text>
    </xsl:for-each>

    <osl:template match="/*">
      <xsl:text>
</xsl:text>
      <xsl:apply-templates select="$docroot" mode="xst:convert">
        <xsl:with-param name="tagdefs" select="$tagdefs"/>
      </xsl:apply-templates>
    </osl:template>
    <xsl:text>
</xsl:text>

    <xsl:apply-templates select="$handler-calls" mode="xst:handler-call">
      <xsl:with-param name="tagdefs" select="$tagdefs"/>
    </xsl:apply-templates>
  </osl:stylesheet>
  <xsl:text>
</xsl:text>
</xsl:template>


<!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  - Convert tag document
  - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -->
<xsl:template match="xst:variable" mode="xst:convert">
  <xsl:param name="tagdefs" as="element(xst:tag)*"/>

  <xsl:variable name="children" select="*"/>

  <osl:variable>
    <xsl:apply-templates select="@*" mode="xst:convert">
      <xsl:with-param name="tagdefs" select="$tagdefs"/>
    </xsl:apply-templates>

    <!-- simple type inference from child elements -->
    <xsl:if test="fn:not(@as) and fn:exists($children) and 
            fn:empty($children/self::xst:*) and
            fn:not(text()[fn:matches(.,'\S')])">
      <xsl:choose>
      <xsl:when test="fn:count($children) gt 1">
        <xsl:attribute name="as">element()+</xsl:attribute>
      </xsl:when>
      <xsl:otherwise>
        <xsl:variable name="child-name" as="xs:string?" select="fn:name($children)"/>
        <xsl:variable name="child-tag"  as="element(xst:tag)?"
            select="$tagdefs[@imp-qname eq $child-name]"/>
        <xsl:choose>
        <xsl:when test="fn:exists($child-tag)">
          <xsl:copy-of select="$child-tag/@as"/>
        </xsl:when>
        <xsl:otherwise>
          <xsl:attribute name="as">element()</xsl:attribute>
        </xsl:otherwise>
        </xsl:choose>
      </xsl:otherwise>
      </xsl:choose>
    </xsl:if>

    <xsl:apply-templates select="node()" mode="xst:convert">
      <xsl:with-param name="tagdefs" select="$tagdefs"/>
    </xsl:apply-templates>
  </osl:variable>
</xsl:template>

<xsl:template match="xst:choose|xst:if|xst:otherwise|xst:text|xst:when" mode="xst:convert">
  <xsl:param name="tagdefs" as="element(xst:tag)*"/>

  <xsl:element name="xsl:{fn:local-name(.)}">
      <xsl:copy-of select="@*"/>
      <xsl:apply-templates select="node()" mode="xst:convert">
        <xsl:with-param name="tagdefs" select="$tagdefs"/>
      </xsl:apply-templates>
  </xsl:element>
</xsl:template>

<xsl:template match="xst:import|xst:param" mode="xst:convert"/>

<xsl:template match="xst:*" mode="xst:convert">
  <xsl:message terminate="yes">
      <xsl:text>Unsupported statement: </xsl:text>
      <xsl:value-of select="fn:name(.)"/>
  </xsl:message>
</xsl:template>

<xsl:template match="*" mode="xst:convert">
  <xsl:param name="tagdefs" as="element(xst:tag)*"/>

  <xsl:variable name="tag-name" as="xs:string?" select="fn:name(.)"/>

  <xsl:variable name="called-tag" as="element(xst:tag)?"
      select="$tagdefs[@imp-qname eq $tag-name]"/>

  <xsl:choose>
  <xsl:when test="fn:exists($called-tag)">
    <xsl:variable name="tag-prefix" as="xs:string?"
            select="fn:string($called-tag/@imp-prefix)"/>

    <xsl:variable name="all-paramdefs" as="element(xst:param)*"
            select="$called-tag/xst:param"/>
    <xsl:variable name="handlerdefs"   as="element(xst:param)*"
            select="$all-paramdefs[fn:starts-with(
                fn:normalize-space(@as),'handler(')]"/>
    <xsl:variable name="paramdefs"     as="element(xst:param)*"
            select="$all-paramdefs except $handlerdefs"/>

    <xsl:variable name="param-atts" as="attribute()*"
            select="@*[fn:name(.) = $paramdefs/@name]"/>

    <xsl:variable name="subelements"        as="element()*" select="*"/>
    <xsl:variable name="tag-subelements"    as="element()*"
            select="$subelements[fn:substring-before(fn:name(.),':') eq $tag-prefix]"/>
    <xsl:variable name="nontag-subelements" as="element()*"
            select="$subelements except $tag-subelements"/>

    <xsl:variable name="handler-containers" as="element()*"
            select="$tag-subelements[fn:local-name(.)=$handlerdefs/@name]"/>
    <xsl:variable name="param-containers"   as="element()*"
            select="$tag-subelements[fn:local-name(.)=$paramdefs/@name]"/>

    <xsl:variable name="implicit-content"      as="node()*"
            select="$nontag-subelements | text()[fn:matches(.,'\S')]"/>
    <xsl:variable name="is-implicit-container" as="xs:boolean"
            select="fn:exists($implicit-content)"/>
    <xsl:variable name="implicitdef"           as="element(xst:param)?"
            select="if ($is-implicit-container)
                then $all-paramdefs[@implicit='true']
                else ()"/>

    <xsl:variable name="is-implicit-handler"   as="xs:boolean"
            select="$implicitdef = $handlerdefs"/>
    <xsl:variable name="is-implicit-param"     as="xs:boolean"
            select="fn:exists($implicitdef) and fn:not($is-implicit-handler)"/>

    <xsl:variable name="is-unresolved-implicit" as="xs:boolean"
            select="($is-implicit-container and fn:empty($implicitdef))"/>
    <xsl:variable name="is-invalid-implicit"    as="xs:boolean"
            select="($is-implicit-container and fn:exists($tag-subelements))"/>
    <xsl:variable name="is-confused-implicit"   as="xs:boolean"
            select="($is-implicit-param and fn:exists(@xst:handles))"/>

    <xsl:variable name="unresolved-atts"       as="attribute()*"
            select="@* except ($param-atts|@xst:handles)"/>
    <xsl:variable name="unresolved-containers" as="element()*"
            select="$tag-subelements except ($handler-containers|$param-containers)"/>

    <xsl:variable name="confused-params" as="element()*"
            select="$param-containers[fn:exists(@xst:handles)]"/>

    <xsl:variable name="param-names"   as="xs:string*"
            select="($param-atts/fn:name(.), $param-containers/fn:local-name(.))"/>
    <xsl:variable name="handler-names" as="xs:string*"
            select="$handler-containers/fn:local-name(.)"/>

    <xsl:variable name="missing-params" as="element(xst:param)*"
            select="(if ($is-implicit-param)
                then ($paramdefs except $implicitdef)
                else $paramdefs)
                [fn:not(@name=$param-names) and
                    fn:matches(@as,'[^?*]\s*$')]"/>
    <xsl:variable name="missing-handlers" as="element(xst:param)*"
            select="(if ($is-implicit-handler)
                then ($handlerdefs except $implicitdef)
                else $handlerdefs)
                [fn:not(@name=$handler-names) and
                    fn:matches(@as,'[^?*]\s*$')]"/>

    <xsl:choose>
    <xsl:when test="fn:empty($unresolved-atts) and
            fn:empty($unresolved-containers) and
            fn:empty($confused-params) and
            fn:empty($missing-params) and
            fn:empty($missing-handlers) and
            fn:not($is-unresolved-implicit) and
            fn:not($is-invalid-implicit) and
            fn:not($is-confused-implicit)">
      <osl:call-template name="{$called-tag/@name}">
        <xsl:apply-templates select="$param-atts" mode="xst:attribute-param">
          <xsl:with-param name="paramdefs" select="$paramdefs"/>
        </xsl:apply-templates>

        <xsl:apply-templates select="$param-containers" mode="xst:container-param">
          <xsl:with-param name="tagdefs"   select="$tagdefs"/>
          <xsl:with-param name="paramdefs" select="$paramdefs"/>
        </xsl:apply-templates>

        <xsl:choose>
        <xsl:when test="fn:exists($handler-containers)">
          <xsl:apply-templates select="." mode="xst:caller-param">
            <xsl:with-param name="tag-name"           select="$tag-name"/>
            <xsl:with-param name="called-tag"         select="$called-tag"/>
            <xsl:with-param name="handler-names"      select="$handler-names"/>
            <xsl:with-param name="handler-containers" select="$handler-containers"/>
            <xsl:with-param name="is-call"            select="fn:false()"/>
          </xsl:apply-templates>
        </xsl:when>
        <xsl:when test="$is-implicit-handler">
          <xsl:apply-templates select="." mode="xst:caller-param">
            <xsl:with-param name="tag-name"           select="$tag-name"/>
            <xsl:with-param name="called-tag"         select="$called-tag"/>
            <xsl:with-param name="handler-names"      select="$implicitdef/@name"/>
            <xsl:with-param name="handler-containers" select="."/>
            <xsl:with-param name="is-call"            select="fn:false()"/>
          </xsl:apply-templates>
        </xsl:when>
        <xsl:when test="$is-implicit-param">
          <osl:with-param>
            <xsl:copy-of select="$implicitdef/(@name|@as)"/>
            <xsl:apply-templates select="node()" mode="xst:convert">
              <xsl:with-param name="tagdefs" select="$tagdefs"/>
            </xsl:apply-templates>
          </osl:with-param>
        </xsl:when>
        </xsl:choose>
      </osl:call-template>
    </xsl:when>
    <xsl:otherwise>
      <xsl:if test="fn:exists($unresolved-atts)">
        <xsl:message terminate="yes">
          <xsl:value-of select="$tag-name"/>
          <xsl:text> has attributes that don't match tag parameters: </xsl:text>
          <xsl:value-of select="fn:string-join($unresolved-atts/fn:name(.),', ')"/>
        </xsl:message>
      </xsl:if>
      <xsl:if test="fn:exists($unresolved-containers)">
        <xsl:message terminate="yes">
          <xsl:value-of select="$tag-name"/>
          <xsl:text> has subelements that don't match tag parameters: </xsl:text>
          <xsl:value-of select="fn:string-join($unresolved-containers/name(.),', ')"/>
        </xsl:message>
      </xsl:if>
      <xsl:if test="fn:exists($confused-params)">
        <xsl:message terminate="yes">
          <xsl:value-of select="$tag-name"/>
          <xsl:text> tag has parameters with a handles attribute: </xsl:text>
          <xsl:value-of select="fn:string-join($confused-params/name(.),', ')"/>
        </xsl:message>
      </xsl:if>
      <xsl:if test="fn:exists($missing-params)">
        <xsl:message terminate="yes">
          <xsl:value-of select="$tag-name"/>
          <xsl:text> tag does not pass a value for the required parameters: </xsl:text>
          <xsl:value-of select="fn:string-join($missing-params/string(@name),', ')"/>
        </xsl:message>
      </xsl:if>
      <xsl:if test="fn:exists($missing-handlers)">
        <xsl:message terminate="yes">
          <xsl:value-of select="$tag-name"/>
          <xsl:text> tag does not pass a value for the required handlers: </xsl:text>
          <xsl:value-of select="fn:string-join($missing-handlers/string(@name),', ')"/>
        </xsl:message>
      </xsl:if>

      <xsl:if test="$is-unresolved-implicit">
        <xsl:message terminate="yes">
          <xsl:value-of select="$tag-name"/>
          <xsl:text> acts as an implicit container but the tag does not define one</xsl:text>
        </xsl:message>
      </xsl:if>
      <xsl:if test="$is-invalid-implicit">
        <xsl:message terminate="yes">
          <xsl:value-of select="$tag-name"/>
          <xsl:text> acts as an implicit container but has explicit containers</xsl:text>
        </xsl:message>
      </xsl:if>
      <xsl:if test="$is-confused-implicit">
        <xsl:message terminate="yes">
          <xsl:value-of select="$tag-name"/>
          <xsl:text> has a handles attribute for an implicit parameter</xsl:text>
        </xsl:message>
      </xsl:if>

    </xsl:otherwise>
    </xsl:choose>
  </xsl:when>
  <xsl:when test="fn:substring-before(fn:name(.),':') = $tagdefs/@imp-prefix">
    <xsl:message terminate="yes">
      <xsl:value-of select="fn:name(.)"/>
      <xsl:text> uses the tag prefix </xsl:text>
      <xsl:value-of select="fn:substring-before(fn:name(.),':')"/>
      <xsl:text> but does not match a tag definition</xsl:text>
    </xsl:message>
  </xsl:when>
  <xsl:otherwise>
    <xsl:copy>
      <xsl:apply-templates select="@*" mode="xst:convert"/>
      <xsl:apply-templates select="node()" mode="xst:convert">
        <xsl:with-param name="tagdefs" select="$tagdefs"/>
      </xsl:apply-templates>
    </xsl:copy>
  </xsl:otherwise>
  </xsl:choose>
</xsl:template>

<xsl:template match="@*" mode="xst:attribute-param">
  <xsl:param name="paramdefs" as="element(xst:param)*"/>

  <xsl:variable name="param-name" as="xs:string?" select="fn:name(.)"/>

  <xsl:variable name="called-param" as="element(xst:param)?"
          select="$paramdefs[@name eq $param-name]"/>

  <xsl:choose>
  <xsl:when test="fn:exists($called-param)">
    <xsl:variable name="param-type" as="attribute()?"
            select="$called-param/@as"/>

    <osl:with-param>
      <xsl:copy-of select="$called-param/@name | $param-type"/>

      <xsl:attribute name="select" select="xst:cast-as(
            xst:convert-attribute-value(.), $param-type)"/>
    </osl:with-param>
  </xsl:when>
  <xsl:otherwise>
    <xsl:message terminate="yes">
      <xsl:value-of select="$param-name"/>
      <xsl:text> attribute parameter does not exist in the tag definition</xsl:text>
    </xsl:message>
  </xsl:otherwise>
  </xsl:choose>
</xsl:template>

<xsl:template match="*" mode="xst:container-param">
  <xsl:param name="tagdefs"   as="element(xst:tag)*"/>
  <xsl:param name="paramdefs" as="element(xst:param)*"/>

  <xsl:variable name="param-name" as="xs:string?" select="fn:local-name(.)"/>

  <xsl:variable name="called-param" as="element(xst:param)?"
          select="$paramdefs[@name eq $param-name]"/>

  <xsl:choose>
  <xsl:when test="fn:exists($called-param)">
    <osl:with-param>
      <xsl:copy-of select="$called-param/@name | $called-param/@as"/>
      <xsl:apply-templates select="node()" mode="xst:convert">
        <xsl:with-param name="tagdefs" select="$tagdefs"/>
      </xsl:apply-templates>
    </osl:with-param>
  </xsl:when>
  <xsl:otherwise>
    <xsl:message terminate="yes">
      <xsl:value-of select="$param-name"/>
      <xsl:text> subelement parameter does not exist in the tag definition</xsl:text>
    </xsl:message>
  </xsl:otherwise>
  </xsl:choose>
</xsl:template>

<xsl:template match="@xst:version" mode="xst:convert"/>

<xsl:template match="@xst:*" mode="xst:convert">
  <xsl:message terminate="yes">
      <xsl:text>Unsupported attribute: </xsl:text>
      <xsl:value-of select="fn:name(.)"/>
  </xsl:message>
</xsl:template>

<xsl:template match="@*" mode="xst:convert">
  <xsl:copy-of select="."/>
</xsl:template>

<xsl:template match="xst:text/text()" mode="xst:convert">
  <xsl:copy-of select="."/>
</xsl:template>

<xsl:template match="text()" mode="xst:convert">
  <xsl:sequence select="xst:convert-content-expressions(.)"/>
</xsl:template>

<xsl:template match="processing-instruction()" mode="xst:convert">
  <xsl:copy-of select="."/>
</xsl:template>

<xsl:template match="comment()" mode="xst:convert"/>

<xsl:template match="*" mode="xst:handler-call">
  <xsl:param name="tagdefs" as="element(xst:tag)*"/>

  <xsl:variable name="tag-name" as="xs:string?" select="fn:name(.)"/>

  <xsl:variable name="called-tag" as="element(xst:tag)?"
        select="$tagdefs[@imp-qname eq $tag-name]"/>

  <xsl:variable name="tag-prefix" as="xs:string?"
        select="fn:string($called-tag/@imp-prefix)"/>

  <xsl:variable name="call-count" as="xs:integer"
        select="xst:count-calls(., $tag-name)"/>

  <xsl:variable name="all-paramdefs" as="element(xst:param)*"
          select="$called-tag/xst:param"/>
  <xsl:variable name="handlerdefs"   as="element(xst:param)*"
          select="$all-paramdefs[fn:starts-with(
              fn:normalize-space(@as),'handler(')]"/>

  <xsl:variable name="subelements"        as="element()*" select="*"/>
  <xsl:variable name="tag-subelements"    as="element()*"
          select="$subelements[fn:substring-before(fn:name(.),':') eq $tag-prefix]"/>
  <xsl:variable name="nontag-subelements" as="element()*"
          select="$subelements except $tag-subelements"/>

  <xsl:variable name="handler-containers" as="element()*"
          select="$tag-subelements[fn:local-name(.)=$handlerdefs/@name]"/>

  <xsl:variable name="implicit-content"   as="node()*"
          select="$nontag-subelements | text()[fn:matches(.,'\S')]"/>

  <xsl:variable name="implicitdef"        as="element(xst:param)?"
          select="if (fn:exists($implicit-content))
              then $handlerdefs[@implicit='true']
              else ()"/>

  <xsl:choose>
  <xsl:when test="fn:exists($handler-containers)">
    <xsl:for-each select="$handler-containers">
      <xsl:apply-templates select="." mode="xst:handlerdef">
        <xsl:with-param name="tagdefs"      select="$tagdefs"/>
        <xsl:with-param name="tag-name"     select="$tag-name"/>
        <xsl:with-param name="call-count"   select="$call-count"/>
        <xsl:with-param name="handlerdefs"  select="$handlerdefs"/>
        <xsl:with-param name="handler-name" select="fn:local-name(.)"/>
        <xsl:with-param name="is-call"      select="fn:false()"/>
      </xsl:apply-templates>
    </xsl:for-each>
  </xsl:when>
  <xsl:when test="fn:exists($implicitdef)">
    <xsl:apply-templates select="." mode="xst:handlerdef">
      <xsl:with-param name="tagdefs"      select="$tagdefs"/>
      <xsl:with-param name="tag-name"     select="$tag-name"/>
      <xsl:with-param name="call-count"   select="$call-count"/>
      <xsl:with-param name="handlerdefs"  select="$implicitdef"/>
      <xsl:with-param name="handler-name" select="$implicitdef/fn:string(@name)"/>
      <xsl:with-param name="is-call"      select="fn:false()"/>
    </xsl:apply-templates>
  </xsl:when>
  <!-- call with only default handlers has no handler definitions -->
  </xsl:choose>
</xsl:template>


</xsl:stylesheet>